package com.spider.admin.logs.util;

import com.alibaba.fastjson.JSON;
import com.spider.admin.conf.ResultMessage;
import com.spider.admin.exception.SpiderException;
import com.spider.admin.utils.CommonUtil;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.index.reindex.UpdateByQueryRequest;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.metrics.ValueCount;
import org.elasticsearch.search.aggregations.metrics.ValueCountAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * elasticSearch工具类
 */
@Component
public class EsUtil {

    @Autowired
    RestHighLevelClient esClient;


    /**
     * 创建索引 判断索引是否存在，如果存在则返回true，如果不存在，则创建
     * 创建成功返回true
     *
     * @return
     * @throws IOException
     */
    public boolean createIndex(String index) throws SpiderException {
        //1、获取查询索引（库） 的请求
        GetIndexRequest request = new GetIndexRequest(index);
        //2、判断该索引是否存在
        boolean ok = false;
        try {
            boolean flag_exist = esClient.indices().exists(request, RequestOptions.DEFAULT);
            if (flag_exist) {
                //如果存在该索引，则返回true
                return true;
            }
            //如果不存在，该索引，则创建该索引
            //3、创建新建索引（库） 的请求
            CreateIndexRequest createIndexRequest = new CreateIndexRequest(index);
            // 设置索引的 settings，包括时区
            Settings settings = Settings.builder()
                    .put("index.number_of_shards", 3)
                    .put("index.number_of_replicas", 2)
                    .build();
                    createIndexRequest.settings(settings);
            //4、执行请求，获得响应
            CreateIndexResponse createIndexResponse = esClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            ok = createIndexResponse.isAcknowledged();
        } catch (Exception e) {
            throw new SpiderException(ResultMessage.FAILED_ES_EXEC.getCode(), e.getMessage());
        }
        return ok;
    }

    /**
     * 批量插入elasticSearch数据
     *
     * @param index
     * @param logList
     * @return
     * @throws SpiderException
     */
    public <T> boolean insertListEs(String index, List<T> logList) throws SpiderException {
        //1、创建大批量数据插入请求
        BulkRequest request = new BulkRequest();
        //2、设置超时时间
//        request.timeout("10s");

        //3、判断是否存在该索引

        if (createIndex(index)) {
//            没有就创建，有就执行使用
            //4、此时存在该索引，往该索引插入数据
            for (T obj : logList) {

                request.add(
                        new IndexRequest(index)
                                .id(CommonUtil.getUniqueKey())
                                .source(JSON.toJSONString(obj), XContentType.JSON));
            }
            try {
                BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);
                return !response.hasFailures();
            } catch (Exception e) {
                throw new SpiderException(ResultMessage.FAILED_ES_EXEC.getCode(), e.getMessage());
            }
        }

        return false;
    }


    /**
     * 根据条件等值匹配删除
     *
     * @param indexName 索引
     * @param map       条件字段映射
     */
    public long deleteEsData(String indexName, Map<String, Object> map) throws SpiderException {
        /*自定义条件删除：
        　　通过QueryBuilders中的termQuery（等值匹配）、rangeQuery（范围匹配）、wildcardQuery（模糊匹配）指定搜索条件
        　　通过QueryBuilders中的boolQuery中的should、must来设置and、or逻辑
        　　通过DeleteByQueryRequest来构建删除请求，setQuery来装载条件，indices来指定索引
        　　通过deleteByQuery来发起删除请求（es也是先查询后删除）
        */
        DeleteByQueryRequest request = new DeleteByQueryRequest();
        request.indices(indexName);
        // 版本冲突
        request.setConflicts("proceed");
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        Set<Map.Entry<String, Object>> entries = map.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            Object value = entry.getValue();
            /*
                坑：
                    未加 .keyword发现删除BulkByScrollResponse->deleted为0
                    定位原因发现是应该 termsQuery进行了分词导致的
                    在出现分词查询，key 添加keyword，只适用于es6，加上keyword就不会进行分词了
                可以修改 es 或者logstash 分词规则。比较好方式修改 es 映射规则
             */
            boolQueryBuilder.must(QueryBuilders.termQuery(key + ".keyword", value));
        }
        request.setQuery(boolQueryBuilder);
        // 删除后刷新
        request.setRefresh(true);
        // 删除数量
        long deleted = 0L;
        try {
            BulkByScrollResponse response = esClient.deleteByQuery(request, RequestOptions.DEFAULT);
            deleted = response.getDeleted();
        } catch (IOException e) {
            throw new SpiderException(ResultMessage.FAILED_ES_EXEC.getCode(), e.getMessage());
        }
        return deleted;
    }

    /**
     * 根据条件等值匹配更新
     *
     * @param indexName    索引
     * @param conditionMap 条件字段映射
     * @param params       修改字段参数映射
     */
    public long updateEsData(String indexName, Map<String, Object> conditionMap, Map<String, Object> params) throws SpiderException {
        /*自定义条件删除：
        　　通过QueryBuilders中的termQuery（等值匹配）、rangeQuery（范围匹配）、wildcardQuery（模糊匹配）指定搜索条件
        　　通过QueryBuilders中的boolQuery中的should、must来设置and、or逻辑
        　　通过DeleteByQueryRequest来构建删除请求，setQuery来装载条件，indices来指定索引
        　　通过deleteByQuery来发起删除请求（es也是先查询后删除）
        */
        UpdateByQueryRequest request = new UpdateByQueryRequest();
        request.indices(indexName);
        // 版本冲突
        request.setConflicts("proceed");
        // 条件构造
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        Set<Map.Entry<String, Object>> entries = conditionMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            Object value = entry.getValue();
            /*
                坑：
                    未加 .keyword发现删除BulkByScrollResponse->deleted为0
                    定位原因发现是应该 termsQuery进行了分词导致的
                    在出现分词查询，key 添加keyword，只适用于es6，加上keyword就不会进行分词了
                可以修改 es 或者logstash 分词规则。比较好方式修改 es 映射规则
             */
            boolQueryBuilder.must(QueryBuilders.termQuery(key + ".keyword", value));
        }
        request.setQuery(boolQueryBuilder);

        // 脚本构造
        StringBuilder script = new StringBuilder();
        Set<String> keys = params.keySet();
        for (String key : keys) {
            String appendValue = "";
            Object value = params.get(key);
            if (value instanceof Number) {
                appendValue = value.toString();
            } else if (value instanceof String) {
                appendValue = "'" + value.toString() + "'";
            } else if (value instanceof List) {
                appendValue = JSON.toJSONString(value);
            } else {
                appendValue = value.toString();
            }
            script.append("ctx._source.").append(key).append("=").append(appendValue).append(";");
        }
        request.setScript(new Script(script.toString()));
        // 更新后刷新
        request.setRefresh(true);
        // 更新数量
        long updated = 0L;
        try {
            BulkByScrollResponse response = esClient.updateByQuery(request, RequestOptions.DEFAULT);
            updated = response.getUpdated();
        } catch (IOException e) {
            throw new SpiderException(ResultMessage.FAILED_ES_EXEC.getCode(), e.getMessage());
        }
        return updated;
    }

    /**
     * 查询公共方法，查询条件在调用方构建
     *
     * @param indexName    索引名称
     * @param pageNum      页码
     * @param pageSize     页大小
     * @param queryBuilder 查询条件
     * @param aClass       期待返回包装类的Class，如果为 null则返回 Map
     * @return
     */
    public <T> List<T> search(
            String indexName,
            int pageNum,
            int pageSize,
            QueryBuilder queryBuilder,
            Map<String,SortOrder> sortMap,
            Class<T> aClass)throws SpiderException {
        //1、条件搜索 参数 索引
        SearchRequest searchRequest = new SearchRequest(indexName);
        //2、构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder
                .from((pageNum - 1) * pageSize)
                .size(pageSize)
                .query(queryBuilder)
                .timeout(new TimeValue(60, TimeUnit.SECONDS));
        if(sortMap!=null&&!sortMap.isEmpty()){
            for(String sortField:sortMap.keySet()){
                searchSourceBuilder.sort(sortField,sortMap.get(sortField));
            }
        }

        //3、注入执行查询条件
//        searchSourceBuilder.query(queryBuilder);
        //4、设置查询超时时间
//        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        //5、执行查询 返回结果
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = null;
        try {

            response = esClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new SpiderException(ResultMessage.FAILED_ES_EXEC.getCode(), e.getMessage());
        }
        return Arrays.stream(response.getHits().getHits()).map(o -> JSON.parseObject(o.getSourceAsString(), aClass)).collect(Collectors.toList());
    }

    /**
     * 查询公共方法，查询条件在调用方构建
     * 查询符合条件的记录总数量
     *
     * @param indexName    索引名称
     * @param queryBuilder 查询条件
     * @return
     */
    public  Long searchTotalNum(String indexName, QueryBuilder queryBuilder) throws SpiderException{
        //1、条件搜索 参数 索引
        SearchRequest searchRequest = new SearchRequest(indexName);
        //2、构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //3、注入执行查询条件
        searchSourceBuilder.query(queryBuilder);
        //4、设置查询超时时间
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));


        // 添加聚合查询，计算满足条件的记录数
        ValueCountAggregationBuilder aggregation = AggregationBuilders.count("count_agg").field("vid");
        searchSourceBuilder.aggregation(aggregation);

        // 设置查询请求的源码
        searchRequest.source(searchSourceBuilder);

        Long totalCount=0L;
        try {
            // 执行查询
            SearchResponse searchResponse = esClient.search(searchRequest, RequestOptions.DEFAULT);

            // 获取聚合查询结果
            Aggregations aggregations = searchResponse.getAggregations();
            ValueCount countAgg = aggregations.get("count_agg");
            totalCount=countAgg.getValue();

        } catch (IOException e) {
            throw new SpiderException(ResultMessage.FAILED_ES_EXEC.getCode(), e.getMessage());
        }
        return totalCount;
    }
    /**
     * 查询公共方法，查询条件在调用方构建
     *
     * @param indexName    索引名称
     * @param pageNum      页码
     * @param pageSize     页大小
     * @param queryBuilder 查询条件
     * @return
     */
    public List<Map<String, Object>> search(String indexName, int pageNum, int pageSize, QueryBuilder queryBuilder) {
        //1、条件搜索 参数 索引
        SearchRequest searchRequest = new SearchRequest(indexName);
        //2、构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.from((pageNum - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        //3、注入执行查询条件
        searchSourceBuilder.query(queryBuilder);
        //4、设置查询超时时间
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        //5、执行查询 返回结果
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = null;
        try {
            response = esClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return Arrays.stream(response.getHits().getHits()).map(o -> o.getSourceAsMap()).collect(Collectors.toList());
    }

    /**
     * 根据key关键词 搜索es中 title标题为key的信息（根据自己需求修改字段 title）
     *
     * @param index    索引
     * @param key      关键词
     * @param pageNum  分页页码
     * @param pageSize 分页 每页的条数
     * @return
     * @throws IOException
     */
    public List<Map<String, Object>> searchEs(String index, String key, int pageNum, int pageSize) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        //1、条件搜索 参数 索引
        SearchRequest searchRequest = new SearchRequest(index);
        //2、构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //3、分页
        int begin = (pageNum - 1) * pageSize;
        searchSourceBuilder.from(begin);
        searchSourceBuilder.size(pageSize);

        //4、查询条件 全文搜索
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", key);
        //5、注入执行查询条件
        searchSourceBuilder.query(matchQueryBuilder);
        //6、设置查询超时时间
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        //7、执行查询 返回结果
        searchRequest.source(searchSourceBuilder);
        SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(response);
        for (SearchHit hit : response.getHits().getHits()) {
            //遍历查询结果
            System.out.println(hit.getSourceAsMap());
            Map<String, Object> map = hit.getSourceAsMap();
            System.out.println(map);
            list.add(map);
        }
        //返回结果
        return list;
    }

    /**
     * 同上面的功能基本一样，添加了高亮显示功能
     *
     * @param index
     * @param key
     * @param pageNum
     * @param pageSize
     * @return
     * @throws IOException
     */
//    public List<Map<String, Object>> searchTitleHighlight(String index, String key, int pageNum, int pageSize, String cusNumber) throws IOException {
//        List<Map<String, Object>> list = new ArrayList<>();
//        //1、条件搜索 参数 索引
//        SearchRequest searchRequest = new SearchRequest(index);
//        //2、构建搜索条件
//        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//        //3、分页
//        int begin = (pageNum - 1) * pageSize;
//        searchSourceBuilder.from(begin);
//        searchSourceBuilder.size(pageSize);
//
//        //4、查询条件 全文搜索
//        BoolQueryBuilder matchQueryBuilder = QueryBuilders.boolQuery();
//        matchQueryBuilder.must(QueryBuilders.matchQuery("type", key));
//        if (cusNumber != null) {
//            matchQueryBuilder.must(QueryBuilders.matchQuery("cus_number", cusNumber));
//        }
////        matchQueryBuilder.should(QueryBuilders.matchQuery("type", key));
//
//
//        //高亮
//        HighlightBuilder highlightBuilder = new HighlightBuilder();
//        highlightBuilder.field("title");
//        //前缀 后缀
//        highlightBuilder.preTags("<p class='light' style='color:red'>");
//        highlightBuilder.postTags("</p>");
//        searchSourceBuilder.highlighter(highlightBuilder);
//        highlightBuilder.requireFieldMatch(false);//一个文档只显示一个高亮
//        //5、注入执行查询条件
//        searchSourceBuilder.query(matchQueryBuilder);
//        //6、设置查询超时时间
//        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//
//        //7、执行查询 返回结果
//        searchRequest.source(searchSourceBuilder);
//        SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
//        for (SearchHit hit : response.getHits().getHits()) {
//            //遍历查询结果
//            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//            HighlightField title = highlightFields.get("title");
//            Map<String, Object> map = hit.getSourceAsMap();//原来的结果
//
//            //解析高亮字段 将之前没有高亮的字段替换为现在高亮的字段即可
//            if (title != null) {
//                Text[] fragments = title.fragments();
//                String newTitle = "";
//                for (Text fragment : fragments) {
//                    newTitle += fragment;
//                }
//                hit.getSourceAsMap().put("title", newTitle);
//            }
//            list.add(map);
//        }
//        //返回结果
//        return list;
//    }
//
//    public List<Map<String, Object>> searchDetailHighlight(String index, String key, int pageNum, int pageSize, String cusNumber) throws IOException {
//        SearchRequest searchRequest = new SearchRequest(index);
//        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//
//        MultiMatchQueryBuilder multiMatchQuery = QueryBuilders
//                .multiMatchQuery(key, "title", "type")
//                //默认是OR
//                .operator(Operator.AND);
//        HighlightBuilder highlightBuilder = new HighlightBuilder();
//
//
//        HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
//        highlightBuilder.field(highlightTitle);
//        HighlightBuilder.Field highlightFilecontent = new HighlightBuilder.Field("type");
//        highlightBuilder.field(highlightFilecontent);
//
//        highlightBuilder
//                .preTags("<span style=color:red>")
//                .postTags("</span>");
//        searchSourceBuilder.highlighter(highlightBuilder);
//        searchSourceBuilder.query(multiMatchQuery);
//        //分页
//        int begin = (pageNum - 1) * pageSize;
//        searchSourceBuilder.from(begin);
//        searchSourceBuilder.size(pageSize);
//        searchRequest.source(searchSourceBuilder);
//        ArrayList<Map<String, Object>> resultList = new ArrayList<>();
//
//        SearchResponse searchResponse = esClient
//                .search(searchRequest, RequestOptions.DEFAULT);
//        SearchHits hits = searchResponse.getHits();
//        SearchHit[] searchHits = hits.getHits();
//
//
//        for (SearchHit hit : searchHits) {
//
//            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//            String source = hit.getSourceAsString();
//
//            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//            HighlightField hTitle = highlightFields.get("title");
//
//            if (hTitle != null) {
//                String hBrandText = "";
//                Text[] fragments = hTitle.fragments();
//                for (Text text : fragments) {
//                    hBrandText += text;
//                }
//                sourceAsMap.put("title", hBrandText);
//            }
//            HighlightField hFilecontent = highlightFields.get("detail");
//            if (hFilecontent != null) {
//                String hNametText = "";
//                Text[] fragments = hFilecontent.fragments();
//                for (Text text : fragments) {
//                    hNametText += text;
//                }
//                sourceAsMap.put("detail", hNametText);
//            }
//            resultList.add(sourceAsMap);
//        }
//        return resultList;
//    }


}
